Author

DaaniiH

Published

June 1, 2025

1 Libraries

Code anzeigen
#| label: load_libraries


library(readr)
library(readxl)
library(pxmake)   # to load PX files
library(pxR)      # to load PX files
library(jsonlite) # to load JSON files

library(purrr)

library(writexl)  # Write Excel files

library(dplyr)
library(tidyverse)
library(ggplot2)  # Diagrams
library(ggforce)  # Diagrams
library(DT)       # datatable()

library(DescTools)

2 Functions

Code anzeigen
#| label: load_functions


### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###  
# Duplikate in df finden & als $unique oder $duplicates selektionierbar machen
find_and_remove_duplicates <- function(df) {
  list(
    unique = df[!duplicated(df), ],
    duplicates = df[duplicated(df) | duplicated(df, fromLast = TRUE), ]
  )
}



### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###  
# Aktive National- & Ständeräte identifizieren und selektionierbar machen

filter_active <- function(data, von, bis) {
  von <- as.Date(von)
  bis <- as.Date(bis)
  data %>%
    filter(
      DateJoining <= bis,                             # Eintritt vor oder am Ende des Zeitraums
      is.na(DateLeaving) | DateLeaving >= von         # Kein Austritt oder Austritt nach Beginn des Zeitraums
    )
}




### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###  
# Duplikate in df finden & als $unique oder $duplicates selektionierbar machen

3 ETL: Extract, Transform, Load

  • Daten werden aus verschiedenen Excel-Dateien ausgelesen, unabhängig davon, wie komplex oder verschachtelt sie sind (z. B. mehrere Sheets, verbundene Zellen, unterteilte Datenblöcke).

  • Bei Excel-Dateien mit mehreren Sheets werden Functions angewendet.

  • Header werden identifiziert und ggf. aus mehreren Zeilen zusammengesetzt.

  • Daten werden bereinigt, normalisiert und in das gewünschte Zielformat gebracht (z. B. Wide zu Long, Entfernen von Leerzeilen, Auflösen von verbundenen Zellen, Vereinheitlichung der Spaltenstruktur).

  • Die transformierten Daten werden für die weitere Analyse und Visualisation zu Verfügung gestellt.

3.1 Lookup tables

3.1.1 Parteinamen

Die verschiedenen Quellen verwenden teils unteschiedliche Parteibezeichnungen. Zur Vereinheitlichung wird deshalb initial eine Lookup Tabelle gebildet.

Code anzeigen
#| label: load_parties


# Dataframe bilden aus zwei Vektoren
parteinamen <- data.frame(
  kuerzel = c("fdp", "sps", "svp", "mitte", "evp", "gps", "glp", "ucsp", "pda", "sd", "edu", "fps", "lega", "kvp", "mcg", "cvp", "bdp", "lps", "ldu", "poch", "rep", "eco", "sgv", "sbv", "sgb", "travs", "sav", "vsa", "vpod", "voev", "tcs", "vcs", "acs", "sbk", "ssv", "gem", "kdk", "kkjpd", "gdk", "ldk"),
  
  name = c("Freisinnig-demokratische Partei (FDP.Die Liberalen)", "Sozialdemokratische Partei", "Schweizerische Volkspartei (bis 1936 Parolen der BGB Bern)", "Die Mitte", "Evangelische Volkspartei", "Grüne Partei der Schweiz", "Grünliberale Partei", "Christlichsoziale Partei der Schweiz (von der CVP unabhängige CSP)", "Partei der Arbeit", "Schweizer Demokraten", "Eidgenössisch-Demokratische Union", "Autopartei", "Lega dei Ticinesi", "Katholische Volkspartei", "Mouvement Citoyens Genevois", "Christlichdemokratische Volkspartei", "Bürgerlich-Demokratische Partei", "Liberale Partei der Schweiz", "Landesring der Unabhängigen", "Progressive Organisationen der Schweiz", "Schweizerische Republikanische Bewegung", "Economiesuisse (bis 15.9.2000: Schweizerischer Handels- und Industrieverein SHIV (Vorort))", "Schweizerischer Gewerbeverband", "Schweizer Bauernverband", "Schweizerischer Gewerkschaftsbund", "Travail.Suisse (bis 2002: Parolen des Christlichnationalen Gewerkschaftsbunds (CNG); dieser fusionierte per 1.1.2003 mit der VSA zu Travail.Suisse)", "Schweizerischer Arbeitgeberverband (bis 1996: Zentralverband Schweizerischer Arbeitgeber-Organisationen ZSAO)", "Vereinigung schweizerischer Angestelltenverbände", "Verband des Personals öffentlicher Dienste", "Verband öffentlicher Verkehr", "Touring Club Schweiz", "Verkehrs-Club der Schweiz", "Automobil Club der Schweiz", "Schweizer Bischofskonferenz", "Schweizerischer Städteverband", "Schweizerischer Gemeindeverband", "Konferenz der Kantonsregierungen", "Konferenz der kantonalen Justiz- und Polizeidirektoren", "Schweizerische Gesundheitsdirektorenkonferenz", "Konferenz der kantonalen Landwirtschaftsdirektoren"),
  stringsAsFactors = FALSE)


# Parteikürzel in Grossbuchstaben 
parteinamen$kuerzel <-  toupper(parteinamen$kuerzel)


# Neue Spalte mit "p-" Präfix für späteren Lookup der Parteiparolen
parteinamen$kuerzel_vote_parole <- paste0("p-", parteinamen$kuerzel) 


# Spaltenreihenfolge anpassen: p_kuerzel, kuerzel, name
parteinamen <- parteinamen[, c("kuerzel_vote_parole",
                               "kuerzel",
                               "name")]

# Anzeige mit datatable()
datatable(parteinamen,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))

Kantone

Code anzeigen
#lookup_elec_nationalrat_canton 

#lookup_elec_nationalrat_parties 

3.2 Abstimmungen

Code anzeigen
#| label: load_voting


# Schweizweite Abstimmungen
voting_raw <- read_delim("~/CAS/Zertifikatsarbeit/data/votes/abstimmungen_swissvotes_DATASET CSV 09-02-2025.csv",
                     delim = ";",
                     escape_double = FALSE,
                     trim_ws = TRUE,
                     show_col_types = FALSE)

# Datum formatieren
voting_raw <- voting_raw %>% 
  mutate(datum = dmy(datum))

# Einträge vor 2020 löschen
voting_5y <- voting_raw %>%
  filter(datum >= as.Date("2020-01-01"))

datatable(voting_5y,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))
Code anzeigen
# Parolen Header extrahieren um Parteinamen für Lookup vorzubereiten
voting_parties <- paste(grep("^p-", names(voting_5y), value = TRUE), collapse = ", ")




voting_5y %>%
  select(anr,                                              # Spalten-Wahl
         datum,
         titel_kurz_d,
         titel_off_d,
         rechtsform, #1 Obligatorisches Referendum
                     #2 Fakultatives Referendum
                     #3 Volksinitiative
                     #4 Direkter Gegenentwurf zu einer Volksinitiative
                     #5 Stichfrage
         dep,
         `br-pos`,   #1 Befürwortend
                     #2 Ablehnend
                     #3 Keine
                     #8 Vorzug für den Gegenentwurf (bei Stichfragen)
                     #9 Vorzug für die Volksinitiative (bei Stichfragen)
                     #. Missing
         legisjahr,
         `pa-iv`,
         `bv-pos`,   #1 Befürwortend
                     #2 Ablehnend
                     #3 Keine Abstimmungsempfehlung des Parlaments
                     #8 Vorzug für den Gegenentwurf (bei Stichfragen)
         `nr-pos`,   #1 Befürwortende Mehrheit im Nationalrat
                     #2 Ablehnende Mehrheit im Nationalrat
                     #3 Keine Abstimmungsempfehlung des Nationalrats
                     #8 Vorzug für den Gegenentwurf (bei Stichfragen)
         nrja,
         nrnein,
         `sr-pos`,   #1 Befürwortende Mehrheit im Ständerat
                     #2 Ablehnende Mehrheit im Ständerat
                     #3 Keine Abstimmungsempfehlung des Ständerats
                     #8 Vorzug für den Gegenentwurf (bei Stichfragen)
         srja,
         srnein,
         # Parteiparolen
         starts_with("p-"),
                     #1 Ja-Parole
                     #2 Nein-Parole
                     #3 keine Parole abzugeben
                     #4 empfahl, einen leeren Stimmzettel einzulegen
                     #5 Stimmfreigabe
                     #8 Bevorzugung des Gegenentwurfs (bei Stichfrage)
                     #9 Bevorzugung der Volksinitiative (bei Stichfrage)
                     #66 Neutral: keine Parole oder Empfehlung auf leer einlegen                        #9999 Organisation existiert nicht
                     #. Unbekannt
         # Parteiparolen Kanton / Parteisektion
         starts_with("p-dev-"),
                     #1 Ja-Parole
                     #2 Nein-Parole
                     #3 keine Parole abzugeben
                     #4 empfahl, einen leeren Stimmzettel einzulegen
                     #5 Stimmfreigabe
                     #8 Bevorzugung des Gegenentwurfs (bei Stichfrage)
                     #9 Bevorzugung der Volksinitiative (bei Stichfrage)
                     # [leer] Parole gleich wie Mutterpartei (oder unbekannt)         
         volk,       #0 Eine Mehrheit der Abstimmenden hat die Vorlage abgelehnt
                     #1 Eine Mehrheit der Abstimmenden hat die Vorlage angenommen
                     #8 Bei Stichfragen:  Mehrheit für Gegenentwurfs
                     #9 Bei Stichfragen:  Mehrheit Volksinitiative
         stand,
                     #0 Die Vorlage hat keine Mehrheit der Standesstimmen er-reicht
                     #1 Die Vorlage hat die Mehrheit der Standesstimmen erreicht
                     #3 Ständemehr nicht notwendig
                     #8 Bei Stichfragen: Mehrheit für Gegenentwurf
                     #9 Bei Stichfragen: Mehrheit für Volksinitiative
         annahme,    #0 Ablehnung der Vorlage
                     #1 Annahme der Vorlage
                     #8 Bei Stichfragen: Gegenentwurf angenommen
                     #9 Bei Stichfragen: Volksinitiative angenommen
                     #. Bei Stichfragen: Ergebnis der Stichfrage obsolet
         berecht,
         stimmen,
         bet,
         leer,
         ungultig,
         gultig,
         volkja,
         volknein,
         `volkja-proz`)
# A tibble: 49 × 84
     anr datum      titel_kurz_d titel_off_d rechtsform dep   `br-pos` legisjahr
   <dbl> <date>     <chr>        <chr>            <dbl> <chr> <chr>    <chr>    
 1   629 2020-02-09 Initiative … Volksiniti…          3 6     2        2019-2023
 2   630 2020-02-09 Verbot der … Strafgeset…          2 3     1        2019-2023
 3   631 2020-09-27 Begrenzungs… Volksiniti…          3 3     2        2019-2023
 4   632 2020-09-27 Jagdgesetz   Änderung d…          2 7     1        2019-2023
 5   633 2020-09-27 Erhöhung de… Änderung d…          2 5     1        2019-2023
 6   634 2020-09-27 Vaterschaft… Änderung d…          2 2     1        2019-2023
 7   635 2020-09-27 Beschaffung… Bundesbesc…          2 4     1        2019-2023
 8   636 2020-11-29 Konzernvera… Volksiniti…          3 3     2        2019-2023
 9   637 2020-11-29 «Kriegsgesc… Volksiniti…          3 6     2        2019-2023
10   638 2021-03-07 Initiative … Volksiniti…          3 3     2        2019-2023
# ℹ 39 more rows
# ℹ 76 more variables: `pa-iv` <dbl>, `bv-pos` <dbl>, `nr-pos` <dbl>,
#   nrja <chr>, nrnein <chr>, `sr-pos` <dbl>, srja <chr>, srnein <chr>,
#   `p-fdp` <chr>, `p-sps` <chr>, `p-svp` <chr>, `p-mitte` <dbl>,
#   `p-evp` <chr>, `p-gps` <chr>, `p-glp` <dbl>, `p-pda` <chr>, `p-sd` <chr>,
#   `p-edu` <chr>, `p-fps` <chr>, `p-lega` <chr>, `p-kvp` <chr>, `p-mcg` <chr>,
#   `p-ucsp` <chr>, `p-cvp` <chr>, `p-bdp` <dbl>, `p-lps` <chr>, …
Code anzeigen
voting_5y_dates <- voting_5y %>%
  select(datum) %>%
  distinct() %>%
  pull(datum) %>%
  as.character()

3.3 Wahlen

3.3.1 Bundesebene

3.3.1.1 Nationalrat

Code anzeigen
#| label: load_nationalrat


# Nationalrat: Daten einlesen
elec_nationalrat  <- read_excel("~/CAS/Zertifikatsarbeit/data/elections/Ratsmitglieder_1848_DE_BUND_NR.xlsx")

# Struktur prüfen
str(elec_nationalrat)
tibble [492 × 21] (S3: tbl_df/tbl/data.frame)
 $ Active               : logi [1:492] TRUE FALSE FALSE TRUE TRUE FALSE ...
 $ FirstName            : chr [1:492] "Jean-Luc" "Jean-Luc" "Jean-Luc" "Cyril" ...
 $ LastName             : chr [1:492] "Addor" "Addor" "Addor" "Aellen" ...
 $ GenderAsString       : chr [1:492] "m" "m" "m" "m" ...
 $ CantonName           : chr [1:492] "Wallis" "Wallis" "Wallis" "Genf" ...
 $ CantonAbbreviation   : chr [1:492] "VS" "VS" "VS" "GE" ...
 $ CouncilName          : chr [1:492] "Nationalrat" "Nationalrat" "Nationalrat" "Nationalrat" ...
 $ ParlGroupName        : chr [1:492] "Fraktion der Schweizerischen Volkspartei" "Fraktion der Schweizerischen Volkspartei" "Fraktion der Schweizerischen Volkspartei" "FDP-Liberale Fraktion" ...
 $ ParlGroupAbbreviation: chr [1:492] "V" "V" "V" "RL" ...
 $ PartyName            : chr [1:492] "Schweizerische Volkspartei" "Schweizerische Volkspartei" "Schweizerische Volkspartei" "FDP.Die Liberalen" ...
 $ PartyAbbreviation    : chr [1:492] "SVP" "SVP" "SVP" "FDP-Liberale" ...
 $ MaritalStatusText    : chr [1:492] "verheiratet" "verheiratet" "verheiratet" NA ...
 $ Nationality          : chr [1:492] "Schweiz,Italien" "Schweiz,Italien" "Schweiz,Italien" "Schweiz" ...
 $ BirthPlace_City      : chr [1:492] "Lausanne" "Lausanne" "Lausanne" "Genf" ...
 $ BirthPlace_Canton    : chr [1:492] "Waadt" "Waadt" "Waadt" "Genf" ...
 $ Mandates             : chr [1:492] "Vice-président de l'UDC du Valais romand; Membre du Comité central de l'UDC Suisse; Député au Grand Conseil: 20"| __truncated__ "Vice-président de l'UDC du Valais romand; Membre du Comité central de l'UDC Suisse; Député au Grand Conseil: 20"| __truncated__ "Vice-président de l'UDC du Valais romand; Membre du Comité central de l'UDC Suisse; Député au Grand Conseil: 20"| __truncated__ "PLR Genève" ...
 $ DateJoining          : chr [1:492] "04.12.2023" "30.11.2015" "02.12.2019" "04.12.2023" ...
 $ DateLeaving          : chr [1:492] NA "01.12.2019" "03.12.2023" NA ...
 $ Citizenship          : chr [1:492] "Sainte-Croix (VD),Savièse (VS)" "Sainte-Croix (VD),Savièse (VS)" "Sainte-Croix (VD),Savièse (VS)" "Genf (GE)" ...
 $ DateOfBirth          : chr [1:492] "22.04.1964" "22.04.1964" "22.04.1964" "29.02.1972" ...
 $ DateOfDeath          : logi [1:492] NA NA NA NA NA NA ...
Code anzeigen
# Fehlende Werte (NA), Klassen und Levels prüfen
Abstract(elec_nationalrat)
────────────────────────────────────────────────────────────────────────────── 
elec_nationalrat

data frame: 492 obs. of  21 variables
        0 complete cases (0.0%)

  Nr  Class  ColName                NAs           Levels
  1   log    Active                   .                 
  2   chr    FirstName                .                 
  3   chr    LastName                 .                 
  4   chr    GenderAsString           .                 
  5   chr    CantonName               .                 
  6   chr    CantonAbbreviation       .                 
  7   chr    CouncilName              .                 
  8   chr    ParlGroupName            2 (0.4%)          
  9   chr    ParlGroupAbbreviation    2 (0.4%)          
  10  chr    PartyName                .                 
  11  chr    PartyAbbreviation        .                 
  12  chr    MaritalStatusText      324 (65.9%)         
  13  chr    Nationality              1 (0.2%)          
  14  chr    BirthPlace_City          2 (0.4%)          
  15  chr    BirthPlace_Canton       17 (3.5%)          
  16  chr    Mandates                47 (9.6%)          
  17  chr    DateJoining              .                 
  18  chr    DateLeaving            204 (41.5%)         
  19  chr    Citizenship              4 (0.8%)          
  20  chr    DateOfBirth              .                 
  21  log    DateOfDeath            492 (100.0%)        
Code anzeigen
PlotMiss(elec_nationalrat)    

Code anzeigen
# Duplikate mittels Funktion ermitteln
elec_nationalrat_duplicates <- find_and_remove_duplicates(elec_nationalrat)
    # elec_nationalrat_duplicates$unique
    # elec_nationalrat_duplicates$duplicates

# df mit unique Einträge weiterverwenden 
elec_nationalrat <- elec_nationalrat_duplicates$unique

  
# Auf relevante Spalten reduzieren und Datumsformat anpassen
    # Spaltenüberschriften konkateniert
    # cat(paste(paste0('"', colnames(elec_nationalrat), '"'),collapse = ",\n"))

elec_nationalrat_final <- elec_nationalrat %>% 
  select(# "Active",
         # "FirstName",
         # "LastName",
         # "GenderAsString",
         "CantonName",
         "CantonAbbreviation",
         # "CouncilName",
         # "ParlGroupName",
         # "ParlGroupAbbreviation",
         "PartyName",
         "PartyAbbreviation",
         # "MaritalStatusText",
         # "Nationality",
         # "BirthPlace_City",
         # "BirthPlace_Canton",
         # "Mandates",
         "DateJoining",
         "DateLeaving",
         # "Citizenship",
         # "DateOfBirth",
         "DateOfDeath") %>% 
  mutate(DateJoining = dmy(DateJoining),
         DateLeaving = dmy(DateLeaving),
         DateOfDeath = dmy(DateOfDeath))  


datatable(elec_nationalrat_final,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))
Code anzeigen
#TODO: in Liste aufnehmen
# Kantone und Parteien extrahieren für die vereinheitlichung der Namen.
lookup_elec_nationalrat_canton <- elec_nationalrat_final %>% 
  select(CantonAbbreviation, CantonName) %>% 
  distinct()

lookup_elec_nationalrat_parties <- elec_nationalrat_final %>% 
  select(PartyAbbreviation, PartyName) %>% 
  distinct()





# TODO: Anwenden für kombinierte Liste NR & SR



#TODO: in Liste aufnehmen
# Kantone und Parteien extrahieren für die vereinheitlichung der Namen.
lookup_elec_nationalrat_canton <- elec_nationalrat_final %>% 
  select(CantonAbbreviation, CantonName) %>% 
  distinct()

lookup_elec_nationalrat_parties <- elec_nationalrat_final %>% 
  select(PartyAbbreviation, PartyName) %>% 
  distinct()


# Abstiummungsdatum (voting_5y_dates) als Spalten hinzufügen
elec_nationalrat_final <- elec_nationalrat_final %>%
  mutate(!!!setNames(rep(list(NA_integer_), length(voting_5y_dates)), voting_5y_dates))


# Ergänzen ob Ratsmitglied zum Zeitpunkt der Abstimmung aktiv war
elec_nationalrat_final <- elec_nationalrat_final %>%
  mutate(!!!setNames(
    lapply(voting_5y_dates, function(datum) {
        abstimmungsdatum <- as.Date(datum)
        with(., as.integer(                            # 1 der 0 zurück 
          DateJoining <= abstimmungsdatum &
            (is.na(DateLeaving) | DateLeaving >= abstimmungsdatum)
        ))
      }),
      voting_5y_dates))









# Sitze pro Kanton, Partei und Jahr/Abstimmung
#     
#     # long format
#       df_sum <- elec_nationalrat_final %>%
#         group_by(CantonAbbreviation, PartyAbbreviation) %>%
#         summarise(sum_value = sum(value, na.rm = TRUE),
#                   .groups = "drop")
#     
#     # wide format
#     # Pivotieren: Partei-Spalten erzeugen
#       df_wide <- df_sum %>%
#         pivot_wider(names_from = PartyAbbreviation,
#                     values_from = sum_value,
#                     values_fill = 0)
#     
#     
#     
#     # Partei-Spaltennamen extrahieren (ohne Kanton/Jahr)
#     partei_cols <- setdiff(names(df_wide),
#                            c("CantonAbbreviation"))
#     
#     # Spaltensummen der Parteien berechnen für Sortierung
#     partei_sums <- colSums(df_wide[partei_cols])
#     
#     # Zeilensumme der Kantone/Jahre hinzufügen
#     df_wide$Total <- rowSums(df_wide[partei_cols])
#     
#     
#     # Parteispalten-Anordnung nach Summe sortieren
#     sorted_partei <- names(sort(partei_sums,
#                                 decreasing = TRUE))
#     
#     
#     # Dataframe neu anordnen: Kanton/Jahr,  sortierte und gefilterte Parteien
#     elec_nationalrat_final <- df_wide[, c(setdiff(names(df_wide),
#                                                   partei_cols),
#                                           sorted_partei)] %>% 
#       arrange(desc(Total))
# 
#     
#         
# datatable(elec_nationalrat_final,
#           class = 'nowrap',
#           filter = 'top',
#           options = list(pageLength = 7,
#                          scrollX = TRUE,
#                          search = list(regex = TRUE,
#                                        caseInsensitive = TRUE)))



# write_xlsx(elec_nationalrat,
#            "elec_nationalrat_final.xlsx")

3.3.1.2 Ständerat

Code anzeigen
#| label: load_ständerat


# Ständerat: Daten einlesen
elec_ständerat  <- read_excel("~/CAS/Zertifikatsarbeit/data/elections/Ratsmitglieder_1848_DE_BUND_SR.xlsx")

str(elec_ständerat)
tibble [155 × 21] (S3: tbl_df/tbl/data.frame)
 $ Active               : logi [1:155] TRUE FALSE TRUE FALSE FALSE FALSE ...
 $ FirstName            : chr [1:155] "Marianne" "Marianne" "Pirmin" "Pirmin" ...
 $ LastName             : chr [1:155] "Binder-Keller" "Binder-Keller" "Bischof" "Bischof" ...
 $ GenderAsString       : chr [1:155] "f" "f" "m" "m" ...
 $ CantonName           : chr [1:155] "Aargau" "Aargau" "Solothurn" "Solothurn" ...
 $ CantonAbbreviation   : chr [1:155] "AG" "AG" "SO" "SO" ...
 $ CouncilName          : chr [1:155] "Ständerat" "Nationalrat" "Ständerat" "Nationalrat" ...
 $ ParlGroupName        : chr [1:155] "Die Mitte-Fraktion. Die Mitte. EVP." "Die Mitte-Fraktion. Die Mitte. EVP." "Die Mitte-Fraktion. Die Mitte. EVP." "Fraktion CVP/EVP/glp" ...
 $ ParlGroupAbbreviation: chr [1:155] "M-E" "M-E" "M-E" "M-E" ...
 $ PartyName            : chr [1:155] "Die Mitte" "Christlichdemokratische Volkspartei der Schweiz" "Die Mitte" "Christlichdemokratische Volkspartei der Schweiz" ...
 $ PartyAbbreviation    : chr [1:155] "M-E" "CVP" "M-E" "CVP" ...
 $ MaritalStatusText    : chr [1:155] "verheiratet" "verheiratet" NA NA ...
 $ Nationality          : chr [1:155] "Schweiz" "Schweiz" "Schweiz" "Schweiz" ...
 $ BirthPlace_City      : chr [1:155] "Zürich" "Zürich" "Solothurn" "Solothurn" ...
 $ BirthPlace_Canton    : chr [1:155] "Zürich" "Zürich" "Solothurn" "Solothurn" ...
 $ Mandates             : chr [1:155] "Mitglied Präsidium Die Mitte Schweiz; Präsidentin Die Mitte Aargau" "Mitglied Präsidium Die Mitte Schweiz; Präsidentin Die Mitte Aargau" "Legislative des Kantons (Kantonsrat): von April 2005 bis November 2007; Exekutive der Gemeinde (Gemeinderat) So"| __truncated__ "Legislative des Kantons (Kantonsrat): von April 2005 bis November 2007; Exekutive der Gemeinde (Gemeinderat) So"| __truncated__ ...
 $ DateJoining          : chr [1:155] "04.12.2023" "02.12.2019" "04.12.2023" "03.12.2007" ...
 $ DateLeaving          : chr [1:155] NA "03.12.2023" NA "04.12.2011" ...
 $ Citizenship          : chr [1:155] "Baden (AG),Untersiggenthal (AG),Zurzach (Bad Zurzach) (AG)" "Baden (AG),Untersiggenthal (AG),Zurzach (Bad Zurzach) (AG)" "Eggersriet (SG),Grub (AR)" "Eggersriet (SG),Grub (AR)" ...
 $ DateOfBirth          : chr [1:155] "15.06.1958" "15.06.1958" "24.02.1959" "24.02.1959" ...
 $ DateOfDeath          : logi [1:155] NA NA NA NA NA NA ...
Code anzeigen
# Anzeige mit datatable()
datatable(elec_ständerat,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))
Code anzeigen
# Fehlende Werte (NA), Klassen und Levels prüfen
Abstract(elec_ständerat)
────────────────────────────────────────────────────────────────────────────── 
elec_ständerat

data frame: 155 obs. of  21 variables
        0 complete cases (0.0%)

  Nr  Class  ColName                NAs           Levels
  1   log    Active                   .                 
  2   chr    FirstName                .                 
  3   chr    LastName                 .                 
  4   chr    GenderAsString           .                 
  5   chr    CantonName               .                 
  6   chr    CantonAbbreviation       .                 
  7   chr    CouncilName              .                 
  8   chr    ParlGroupName            .                 
  9   chr    ParlGroupAbbreviation    .                 
  10  chr    PartyName                .                 
  11  chr    PartyAbbreviation        .                 
  12  chr    MaritalStatusText      116 (74.8%)         
  13  chr    Nationality              .                 
  14  chr    BirthPlace_City          .                 
  15  chr    BirthPlace_Canton        .                 
  16  chr    Mandates                11 (7.1%)          
  17  chr    DateJoining              .                 
  18  chr    DateLeaving             45 (29.0%)         
  19  chr    Citizenship              .                 
  20  chr    DateOfBirth              .                 
  21  log    DateOfDeath            155 (100.0%)        
Code anzeigen
PlotMiss(elec_ständerat)

Code anzeigen
# Datensatz reduzieren auf Aktive und doppelte Einträge elimnieren
elec_ständerat_reduced <- elec_ständerat %>%
  filter(Active == TRUE) %>% 
  distinct(.keep_all = TRUE) %>% 
  select(CantonAbbreviation,
         CantonName,
         PartyAbbreviation,
         PartyName) %>% 
  mutate(value = 1)

datatable(elec_ständerat_reduced,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))
Code anzeigen
# Sitze pro Kanton, Partei und Jahr 
    
    # long format
      df_sum <- elec_ständerat_reduced %>%
      group_by(CantonAbbreviation, PartyAbbreviation) %>%
      summarise(sum_value = sum(value, na.rm = TRUE),
                .groups = "drop")
    
    # wide format
    # Pivotieren: Partei-Spalten erzeugen
    df_wide <- df_sum %>%
      pivot_wider(names_from = PartyAbbreviation,
                  values_from = sum_value,
                  values_fill = 0)
    
    
    # Partei-Spaltennamen extrahieren (ohne Kanton/Jahr)
    partei_cols <- setdiff(names(df_wide),
                           c("CantonAbbreviation"))
    
    # Spaltensummen der Parteien berechnen für Sortierung
    partei_sums <- colSums(df_wide[partei_cols])
    
    # Zeilensumme der Kantone/Jahre hinzufügen
    df_wide$Total <- rowSums(df_wide[partei_cols])
    
    
    # Parteispalten-Anordnung nach Summe sortieren
    sorted_partei <- names(sort(partei_sums,
                                decreasing = TRUE))
    
    
    # Dataframe neu anordnen: Kanton/Jahr,  sortierte und gefilterte Parteien
    elec_ständerat_final <- df_wide[, c(setdiff(names(df_wide),
                                                  partei_cols),
                                        sorted_partei)] %>% 
      arrange(desc(Total))
    
datatable(elec_ständerat_final,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))
Code anzeigen
# write_xlsx(elec_ständerat,
#            "elec_ständerat_final.xlsx")

3.3.2 Kantonsebene

3.3.2.1 Kantonale Parlamente (Legislative)

Code anzeigen
# TODO Die Kantonalen Abstimmungen finden nicht in allen kantonen gleichzeitg statt. Deshalb genauer die Räte/Konstellation zum Zeitpunkt der jeweiligen Abstimmung zu prüfen.


# Struktur der "schön formatierten" Exceldatei lässt keinen "simplen" Import zu.
# Header ist in Zeile 2 und nicht vollständig
# Daten (für Kantone) starten in Zeile 4 aber enden auf Zeile 29 bevor es mit Kommentaren und Fussnoten weitergeht 


# Header aus Zeile 2 für Spaltennamen lesen
tmp_header <- read_excel("~/CAS/Zertifikatsarbeit/data/elections/je-d-17.02.05.01.03_KANTON_Kantonale_Parlamentswahlen.xlsx", 
                     sheet = 1, 
                     skip = 1,   # Header ist Zeile 2
                     n_max = 0) %>%
  names()

# Lücken im Header anpassen (i.e. erste beiden Spalten benennen)
tmp_header <- c("Kanton", "Leer", tmp_header)

tmp_name_map <- c("Kanton"        = "Kanton",
                  "Leer"          = "Leer",
                  "Wahljahr 5)"   = "Wahljahr",
                  "FDP 2)"        = "FDP",
                  "SP"            = "SP",
                  "SVP"           = "SVP",
                  "LPS 2)"        = "LPS",
                  "EVP"           = "EVP",
                  "CSP"           = "CSP",
                  "GLP"           = "GLP",
                  "Die Mitte 8)"  = "Mitte",
                  "CVP 3) 8)"     = "CVP",
                  "BDP 8)"        = "BDP",
                  "PdA"           = "PdA",
                  "PSA"           = "PSA",
                  "Grüne 9)"      = "Grüne",
                  "FGA"           = "FGA",
                  "Sol."          = "Sol.",
                  "EDU"           = "EDU",
                  "Lega"          = "Lega",
                  "MCG (MCR)"     = "MCR",
                  "Übrige 4)"     = "Übrige",
                  "Total"         = "Total")



# Automatisch ersetzen
tmp_header <- ifelse(tmp_header %in% names(tmp_name_map),
                     tmp_name_map[tmp_header],
                     tmp_header)

print(tmp_header)
 [1] "Kanton"   "Leer"     "Wahljahr" "FDP"      "SP"       "SVP"     
 [7] "LPS"      "EVP"      "CSP"      "GLP"      "Mitte"    "CVP"     
[13] "BDP"      "PdA"      "PSA"      "Grüne"    "FGA"      "Sol."    
[19] "EDU"      "Lega"     "MCR"      "Übrige"   "Total"   
Code anzeigen
# Daten ab Zeile 4 importieren
elec_kantonsparlament_raw <- read_excel("~/CAS/Zertifikatsarbeit/data/elections/je-d-17.02.05.01.03_KANTON_Kantonale_Parlamentswahlen.xlsx", 
                 sheet = 1, 
                 skip = 3,         # überspringt die ersten 3 Zeilen
                 col_names = tmp_header)

# Schritt 4: Nur Zeilen behalten, in denen "Wahljahr" nicht NA ist
elec_kantonsparlament <- elec_kantonsparlament_raw  %>%
  filter(!is.na(Wahljahr)) %>% 
  select(-Leer)

# Ergebnis anzeigen
print(elec_kantonsparlament)
# A tibble: 26 × 22
   Kanton   Wahljahr FDP   SP    SVP   LPS   EVP   CSP   GLP   Mitte CVP   BDP  
   <chr>       <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
 1 Zürich       2023 29    36    46    *     7     *     24    11    *     *    
 2 Bern         2022 18    32    44    *     9     *     16    12    *     *    
 3 Luzern       2023 22    19    27    *     0     *     8     32    *     *    
 4 Uri 6)       2024 12    4     17    *     *     *     3     22    *     *    
 5 Schwyz       2024 19    14    38    *     0     *     5     23    *     *    
 6 Obwalden     2022 11    6     13    *     *     *     2     19    *     *    
 7 Nidwald…     2022 16    2     15    *     *     *     5     15    *     *    
 8 Glarus       2022 11    8     18    *     *     *     3     12    *     *    
 9 Zug          2022 18    8     18    *     *     *     6     19    *     *    
10 Freiburg     2021 23    21    18    *     0     4     3     26    *     *    
# ℹ 16 more rows
# ℹ 10 more variables: PdA <chr>, PSA <chr>, Grüne <chr>, FGA <chr>,
#   Sol. <chr>, EDU <chr>, Lega <chr>, MCR <chr>, Übrige <chr>, Total <dbl>

3.3.2.2 Kantonale Regierung (Exekutive)

Die Kantonalen Abstimmungen finden nicht in allen kantonen gleichzeitg statt. Deshalb genauer die Räte/Konstellation zum Zeitpunkt der jeweiligen Abstimmung zu prüfen.

Die Struktur der “schön formatierten” Exceldatei lässt keinen “simplen” Import zu.

  • 1 Excelsheet / Jahr

  • Header ist in Zeile 2 und nicht vollständig

  • Daten (für Kantone) starten in Zeile 4 aber enden auf Zeile 29 bevor es mit Kommentaren und Fussnoten weitergeht.

Dateipfad und gewünschte Sheets festlegen

Code anzeigen
dateipfad <- "~/CAS/Zertifikatsarbeit/data/elections/je-d-17.02.06.01_KANTON_Kantonale_Regierungswahlen.xlsx"  

selected_sheets <- c("2024", "2023", "2022","2021","2020","2019")

Function

Code anzeigen
import_election_data <- function(dateipfad, sheetname) {
  
  # Headerzeilen einlesen  
  header <- readxl::read_excel(dateipfad,
                               sheet = sheetname,
                               skip = 1,
                               n_max = 0) %>%
    names()
  
  # Header anpassen: Wahljahr vereinheitlichen und Kanton hinzufügen
  header[grepl("^Wahljahr", header)] <- "Wahljahr" 
  header <- c("Kanton", header)
        
  # Daten ab Zeile 4 importieren
  daten_raw <- read_excel(dateipfad,
                          sheet = sheetname,
                          skip = 3,
                          col_names = header)
  
  # Zeilen ohne "Wahljahr" ausschliessen
  wahljahr_col <- names(daten_raw)[grepl("^Wahljahr",
                                         names(daten_raw))][1]
  names(daten_raw)[names(daten_raw) == wahljahr_col] <- "Wahljahr"

  daten_wide <- daten_raw %>%
    filter(!is.na(suppressWarnings(as.numeric(.data[[wahljahr_col]]))))
  
  # Zu pivotierenden Spalten in numeric umwandeln
  cols_pivot <- setdiff(names(daten_wide), c("Kanton", wahljahr_col))
  daten_wide <- daten_wide %>%
    mutate(across(all_of(cols_pivot), as.numeric))
  
  # 6. Pivotieren
  daten_long <- daten_wide %>%
    pivot_longer(
      cols = all_of(cols_pivot),
      names_to = "Partei",
      values_to = "Wert")
  
  return(daten_long)
}

Function ausführen: Import und Konsolidation

Code anzeigen
# Objekt erstellen, mit Sheets als tibble/dataframe
daten_liste <- setNames(lapply(selected_sheets,
                               function(sheet) import_election_data (dateipfad,
                                                                     sheet)),
                        selected_sheets)

str(daten_liste)
List of 6
 $ 2024: tibble [494 × 4] (S3: tbl_df/tbl/data.frame)
  ..$ Kanton  : chr [1:494] "Zürich" "Zürich" "Zürich" "Zürich" ...
  ..$ Wahljahr: num [1:494] 2023 2023 2023 2023 2023 ...
  ..$ Partei  : chr [1:494] "FDP 2" "SP" "SVP" "LP 2" ...
  ..$ Wert    : num [1:494] 1 1 2 NA 0 NA 0 1 NA NA ...
 $ 2023: tibble [494 × 4] (S3: tbl_df/tbl/data.frame)
  ..$ Kanton  : chr [1:494] "Zürich" "Zürich" "Zürich" "Zürich" ...
  ..$ Wahljahr: num [1:494] 2023 2023 2023 2023 2023 ...
  ..$ Partei  : chr [1:494] "FDP 2)" "SP" "SVP" "LP 2)" ...
  ..$ Wert    : num [1:494] 1 1 2 NA 0 NA 0 1 NA NA ...
 $ 2022: tibble [494 × 4] (S3: tbl_df/tbl/data.frame)
  ..$ Kanton  : chr [1:494] "Zürich" "Zürich" "Zürich" "Zürich" ...
  ..$ Wahljahr: num [1:494] 2019 2019 2019 2019 2019 ...
  ..$ Partei  : chr [1:494] "FDP 2)" "SP" "SVP" "LP 2)" ...
  ..$ Wert    : num [1:494] 1 1 2 NA 0 NA NA NA 1 0 ...
 $ 2021: tibble [494 × 4] (S3: tbl_df/tbl/data.frame)
  ..$ Kanton  : chr [1:494] "Zürich" "Zürich" "Zürich" "Zürich" ...
  ..$ Wahljahr: num [1:494] 2019 2019 2019 2019 2019 ...
  ..$ Partei  : chr [1:494] "FDP 2)" "SP" "SVP" "LP 2)" ...
  ..$ Wert    : num [1:494] 1 1 2 NA 0 NA NA NA 1 0 ...
 $ 2020: tibble [780 × 4] (S3: tbl_df/tbl/data.frame)
  ..$ Kanton  : chr [1:780] "Zürich" "Zürich" "Zürich" "Zürich" ...
  ..$ Wahljahr: num [1:780] 2019 2019 2019 2019 2019 ...
  ..$ Partei  : chr [1:780] "FDP 2)" "CVP 3)" "SP" "SVP" ...
  ..$ Wert    : num [1:780] 1 1 1 2 NA NA NA 0 NA NA ...
 $ 2019: tibble [780 × 4] (S3: tbl_df/tbl/data.frame)
  ..$ Kanton  : chr [1:780] "Zürich" "Zürich" "Zürich" "Zürich" ...
  ..$ Wahljahr: num [1:780] 2019 2019 2019 2019 2019 ...
  ..$ Partei  : chr [1:780] "FDP 2)" "CVP 3)" "SP" "SVP" ...
  ..$ Wert    : num [1:780] 1 1 2 2 NA NA NA 0 NA NA ...
Code anzeigen
lapply(daten_liste, summary)
$`2024`
    Kanton             Wahljahr       Partei               Wert      
 Length:494         Min.   :2020   Length:494         Min.   :0.000  
 Class :character   1st Qu.:2022   Class :character   1st Qu.:0.000  
 Mode  :character   Median :2023   Mode  :character   Median :1.000  
                    Mean   :2023                      Mean   :1.613  
                    3rd Qu.:2024                      3rd Qu.:2.000  
                    Max.   :2024                      Max.   :7.000  
                                                      NA's   :303    

$`2023`
    Kanton             Wahljahr       Partei               Wert      
 Length:494         Min.   :2020   Length:494         Min.   :0.000  
 Class :character   1st Qu.:2020   Class :character   1st Qu.:0.000  
 Mode  :character   Median :2022   Mode  :character   Median :1.000  
                    Mean   :2022                      Mean   :1.647  
                    3rd Qu.:2023                      3rd Qu.:2.000  
                    Max.   :2023                      Max.   :7.000  
                                                      NA's   :307    

$`2022`
    Kanton             Wahljahr       Partei               Wert      
 Length:494         Min.   :2018   Length:494         Min.   :0.000  
 Class :character   1st Qu.:2020   Class :character   1st Qu.:0.000  
 Mode  :character   Median :2020   Mode  :character   Median :1.000  
                    Mean   :2020                      Mean   :1.613  
                    3rd Qu.:2022                      3rd Qu.:2.000  
                    Max.   :2022                      Max.   :7.000  
                                                      NA's   :303    

$`2021`
    Kanton             Wahljahr       Partei               Wert      
 Length:494         Min.   :2017   Length:494         Min.   :0.000  
 Class :character   1st Qu.:2018   Class :character   1st Qu.:0.000  
 Mode  :character   Median :2020   Mode  :character   Median :1.000  
                    Mean   :2019                      Mean   :1.613  
                    3rd Qu.:2020                      3rd Qu.:2.000  
                    Max.   :2021                      Max.   :7.000  
                                                      NA's   :303    

$`2020`
    Kanton             Wahljahr       Partei               Wert      
 Length:780         Min.   :2016   Length:780         Min.   :0.000  
 Class :character   1st Qu.:2018   Class :character   1st Qu.:0.000  
 Mode  :character   Median :2019   Mode  :character   Median :1.000  
                    Mean   :2019                      Mean   :1.604  
                    3rd Qu.:2020                      3rd Qu.:2.000  
                    Max.   :2020                      Max.   :7.000  
                                                      NA's   :588    

$`2019`
    Kanton             Wahljahr       Partei               Wert      
 Length:780         Min.   :2015   Length:780         Min.   :0.000  
 Class :character   1st Qu.:2016   Class :character   1st Qu.:0.000  
 Mode  :character   Median :2018   Mode  :character   Median :1.000  
                    Mean   :2017                      Mean   :1.596  
                    3rd Qu.:2018                      3rd Qu.:2.000  
                    Max.   :2019                      Max.   :7.000  
                                                      NA's   :587    
Code anzeigen
# Alle tibbles zu einem Dataframe zusammenfügen
elec_kantonale_regierung_combined <- bind_rows(daten_liste,
                                               .id = "Sheet")


datatable(elec_kantonale_regierung_combined,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))
Code anzeigen
# TODO:
# Parteinamen bereinigen!
# finales file: elec_kantonale_regierung_final

# Dataframe als Excel schreiben
# write_xlsx(elec_kantonale_regierung_final,
#            "elec_kantonale_regierung_final.xlsx")

3.3.3 Gemeindeebene

3.3.3.1 Exekutiven der statistischen Städte

  • 1 Excelsheet / Jahr

  • Header zweizeilig mit Start in Zeile –> 3 skip = 2, n_max = 2,

  • Teils verbundene Header-Zeilen (vertikal/horizontal)

  • Cluster Einwohnerzahlen unterteilt Daten

  • Parteinamen uneinheitlich aufgrund von Fussnoten

–> Datenformat “wide” zuerst bereinigen und ins “long” Format bringen

Dateipfad und gewünschte Sheets festlegen

Code anzeigen
dateipfad <- "~/CAS/Zertifikatsarbeit/data/elections/je-d-17.02.07.01_GEMEINDE_Die Exekutiven der statistischen Städte.xlsx"

selected_sheets <- c("2024", "2023", "2022","2021","2020","2019") 

Function für den Import der Gemeindedaten

Code anzeigen
importiere_sheet <- function(dateipfad, sheetname) {

      ###################################################################
      ########## DATEN IMPORTIEREN ######################################
      ########## Header definieren ######################################
      ###################################################################
      
      
      # Headerzeilen einlesen
      header <- suppressMessages(read_excel(dateipfad,
                   sheet = sheetname,
                   skip = 2,
                   n_max = 2,
                   col_names = FALSE))
      
      # Daten  einlesen
      daten_raw <- suppressMessages(read_excel(dateipfad,
                              sheet = sheetname,
                              skip = 5,
                              col_names = FALSE))
      
      
      # Die Spaltennamen zusammensetzen
      # Leere Zellen in 1. Headerzeile mit Werten von rechts auffüllen wenn leer/NA
      header_filled <- as.data.frame(t(header))   # t() "matrix transpose"
      header_filled <- fill(header_filled,        
                            V1,                   # Header Spalte 1 (V1) auffüllen
                            .direction = "down")  # Werte nach unten übernehmen
      header_filled <- t(header_filled)           # t() nochmals (zurück)
      
      # Spaltennamen kombinieren
      spaltennamen <- paste(header_filled[1, ],
                            header_filled[2, ],
                            sep = "_")
      spaltennamen <- gsub("_NA|NA_","", spaltennamen) # Entfernt überflüssige NAs
      
      # Spaltennamen zuweisen
      colnames(daten_raw) <- spaltennamen
      
      
      ###################################################################
      ########## TRANSPONIEREN ##########################################
      ########## Gemeindegrössen Cluster ################################
      ###################################################################
      
      
      # Cluster Einwohnerzahl als Spalte verwenden
      daten_wide <- daten_raw %>%
        mutate(Gemeindegrösse_Cluster =             # Name der neuen Spalte
                 ifelse(
                   is.na(                           # 4) auf NA prüfen
                     suppressWarnings(        # 3) Warnung aus (z.B. "≥ ..Einw..")
                     as.numeric(                    # 2) Wert als Zahl 
                     gsub("'", "", `Kantons-Nr.`)   # 1) Tausender Zeichen (')                                                              entfernen
                   ))),
                   as.character(`Kantons-Nr.`),     # 5) Wert von "Kantons-Nr" 
                   NA_character_                    #    sonst NA   
                 ))  %>%
        fill(Gemeindegrösse_Cluster,
             .direction = "down") %>%              # Cluster nach unten auffüllen
        filter(!is.na(suppressWarnings(
          as.numeric(gsub("'", "", `Kantons-Nr.`))))) # Herausfiltern von Zeilen                                                           mit NICHT-numerischem Wert
    
    
    # Ansicht (Snapshot) nach transponieren
    daten_wide[1:10,                    # 10 Zeilen
               c(1:10,                  # 10 Spalten +
                 ncol(daten_wide))]     # Letzte Spalte
                                        # ncol() von df daten_wide --> Anz. Spalten
                                        # Anzahl Spalten = Position letzte Spalte
    
    
    
    ###################################################################
    ########## TRANSPONIEREN ##########################################
    ########## Frauen, Männer, Total & Parteien ## ####################
    ###################################################################
    
    
    
    daten_long <- daten_wide %>%
      mutate(across(matches("(_Frauen|_Männer|_Total)$"),
                    ~ suppressWarnings(as.numeric(.)))) %>%     # Werte numerisch
      pivot_longer(cols = matches("(_Frauen|_Männer|_Total)$"), # Spaltenauswahl
                   names_to = c("Partei", "m_w_Total"),         # Neue Label-Spalte
                   names_pattern = "^(.*)_(Frauen|Männer|Total)$",  # RegEx
                   values_to = "Wert")                          # Neue Wert-Spalte
    
    return(daten_long)

}

Function ausführen: Import der Dateien testen

Code anzeigen
for (sheet in selected_sheets) {
  cat("\n-----------------------------\n")
  cat("Versuche Sheet:", sheet, "\n")
  result <- tryCatch({
    dat <- importiere_sheet(dateipfad, sheet)
    cat("Sheet erfolgreich eingelesen:", sheet, "\n")
    # Zusammenfassung:
    cat("Anzahl Zeilen:", nrow(dat), "\n")
    cat("Anzahl Spalten:", ncol(dat), "\n")
    cat("Spaltennamen (erste 15):", paste(names(dat)[1:min(15, ncol(dat))], collapse = ", "), "\n")
    TRUE
  }, error = function(e) {
    cat("Fehler beim Einlesen von Sheet:", sheet, "\n")
    cat("Fehlermeldung:", e$message, "\n")
    FALSE
  })
}

-----------------------------
Versuche Sheet: 2024 
Sheet erfolgreich eingelesen: 2024 
Anzahl Zeilen: 7515 
Anzahl Spalten: 10 
Spaltennamen (erste 15): Kantons-Nr., Kanton, Gemeinde-Nr., Gemeinde, Wahljahr, Einwohner, Gemeindegrösse_Cluster, Partei, m_w_Total, Wert 

-----------------------------
Versuche Sheet: 2023 
Sheet erfolgreich eingelesen: 2023 
Anzahl Zeilen: 7776 
Anzahl Spalten: 10 
Spaltennamen (erste 15): Kantons-Nr., Kanton, Gemeinde-Nr., Gemeinde, Wahljahr, Einwohner, Gemeindegrösse_Cluster, Partei, m_w_Total, Wert 

-----------------------------
Versuche Sheet: 2022 
Sheet erfolgreich eingelesen: 2022 
Anzahl Zeilen: 7776 
Anzahl Spalten: 10 
Spaltennamen (erste 15): Kantons-Nr., Kanton, Gemeinde-Nr., Gemeinde, Wahljahr, Einwohner, Gemeindegrösse_Cluster, Partei, m_w_Total, Wert 

-----------------------------
Versuche Sheet: 2021 
Sheet erfolgreich eingelesen: 2021 
Anzahl Zeilen: 7776 
Anzahl Spalten: 10 
Spaltennamen (erste 15): Kantons-Nr., Kanton, Gemeinde-Nr., Gemeinde, Wahljahr, Einwohner, Gemeindegrösse_Cluster, Partei, m_w_Total, Wert 

-----------------------------
Versuche Sheet: 2020 
Sheet erfolgreich eingelesen: 2020 
Anzahl Zeilen: 7290 
Anzahl Spalten: 10 
Spaltennamen (erste 15): Kantons-Nr., Kanton, Gemeinde-Nr., Gemeinde, Wahljahr, Einwohner, Gemeindegrösse_Cluster, Partei, m_w_Total, Wert 

-----------------------------
Versuche Sheet: 2019 
Sheet erfolgreich eingelesen: 2019 
Anzahl Zeilen: 14580 
Anzahl Spalten: 11 
Spaltennamen (erste 15): Kantons-Nr., Kanton, Gemeinde-Nr., Gemeinde, Einwohner, Grössenklasse-Nr., Grössenklasse, Gemeindegrösse_Cluster, Partei, m_w_Total, Wert 

Function ausführen: Import und Konsolidation

Code anzeigen
# Objekt erstellen, mit Sheets als tibble/dataframe
daten_liste <- setNames(lapply(selected_sheets,
                               function(sheet) importiere_sheet(dateipfad, sheet)),
                        selected_sheets)

str(daten_liste)
List of 6
 $ 2024: tibble [7,515 × 10] (S3: tbl_df/tbl/data.frame)
  ..$ Kantons-Nr.           : chr [1:7515] "1" "1" "1" "1" ...
  ..$ Kanton                : chr [1:7515] "ZH" "ZH" "ZH" "ZH" ...
  ..$ Gemeinde-Nr.          : num [1:7515] 230 230 230 230 230 230 230 230 230 230 ...
  ..$ Gemeinde              : chr [1:7515] "Winterthur" "Winterthur" "Winterthur" "Winterthur" ...
  ..$ Wahljahr              : num [1:7515] 2022 2022 2022 2022 2022 ...
  ..$ Einwohner             : num [1:7515] 115129 115129 115129 115129 115129 ...
  ..$ Gemeindegrösse_Cluster: chr [1:7515] "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" ...
  ..$ Partei                : chr [1:7515] "FDP 1" "FDP 1" "FDP 1" "Die Mitte 2" ...
  ..$ m_w_Total             : chr [1:7515] "Frauen" "Männer" "Total" "Frauen" ...
  ..$ Wert                  : num [1:7515] 0 1 1 0 1 1 NA NA NA 1 ...
 $ 2023: tibble [7,776 × 10] (S3: tbl_df/tbl/data.frame)
  ..$ Kantons-Nr.           : chr [1:7776] "1" "1" "1" "1" ...
  ..$ Kanton                : chr [1:7776] "ZH" "ZH" "ZH" "ZH" ...
  ..$ Gemeinde-Nr.          : num [1:7776] 230 230 230 230 230 230 230 230 230 230 ...
  ..$ Gemeinde              : chr [1:7776] "Winterthur" "Winterthur" "Winterthur" "Winterthur" ...
  ..$ Wahljahr              : num [1:7776] 2022 2022 2022 2022 2022 ...
  ..$ Einwohner             : num [1:7776] 115129 115129 115129 115129 115129 ...
  ..$ Gemeindegrösse_Cluster: chr [1:7776] "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" ...
  ..$ Partei                : chr [1:7776] "FDP 1" "FDP 1" "FDP 1" "Die Mitte 2" ...
  ..$ m_w_Total             : chr [1:7776] "Frauen" "Männer" "Total" "Frauen" ...
  ..$ Wert                  : num [1:7776] 0 1 1 0 1 1 NA NA NA 1 ...
 $ 2022: tibble [7,776 × 10] (S3: tbl_df/tbl/data.frame)
  ..$ Kantons-Nr.           : chr [1:7776] "1" "1" "1" "1" ...
  ..$ Kanton                : chr [1:7776] "ZH" "ZH" "ZH" "ZH" ...
  ..$ Gemeinde-Nr.          : num [1:7776] 230 230 230 230 230 230 230 230 230 230 ...
  ..$ Gemeinde              : chr [1:7776] "Winterthur" "Winterthur" "Winterthur" "Winterthur" ...
  ..$ Wahljahr              : num [1:7776] 2022 2022 2022 2022 2022 ...
  ..$ Einwohner             : num [1:7776] 115129 115129 115129 115129 115129 ...
  ..$ Gemeindegrösse_Cluster: chr [1:7776] "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" ...
  ..$ Partei                : chr [1:7776] "FDP 1" "FDP 1" "FDP 1" "Die Mitte 2" ...
  ..$ m_w_Total             : chr [1:7776] "Frauen" "Männer" "Total" "Frauen" ...
  ..$ Wert                  : num [1:7776] 0 1 1 0 1 1 NA NA NA 1 ...
 $ 2021: tibble [7,776 × 10] (S3: tbl_df/tbl/data.frame)
  ..$ Kantons-Nr.           : chr [1:7776] "1" "1" "1" "1" ...
  ..$ Kanton                : chr [1:7776] "ZH" "ZH" "ZH" "ZH" ...
  ..$ Gemeinde-Nr.          : num [1:7776] 230 230 230 230 230 230 230 230 230 230 ...
  ..$ Gemeinde              : chr [1:7776] "Winterthur" "Winterthur" "Winterthur" "Winterthur" ...
  ..$ Wahljahr              : num [1:7776] 2018 2018 2018 2018 2018 ...
  ..$ Einwohner             : num [1:7776] 110912 110912 110912 110912 110912 ...
  ..$ Gemeindegrösse_Cluster: chr [1:7776] "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" ...
  ..$ Partei                : chr [1:7776] "FDP 1" "FDP 1" "FDP 1" "Die Mitte 2" ...
  ..$ m_w_Total             : chr [1:7776] "Frauen" "Männer" "Total" "Frauen" ...
  ..$ Wert                  : num [1:7776] 1 1 2 NA NA NA 0 1 1 2 ...
 $ 2020: tibble [7,290 × 10] (S3: tbl_df/tbl/data.frame)
  ..$ Kantons-Nr.           : chr [1:7290] "1" "1" "1" "1" ...
  ..$ Kanton                : chr [1:7290] "ZH" "ZH" "ZH" "ZH" ...
  ..$ Gemeinde-Nr.          : num [1:7290] 230 230 230 230 230 230 230 230 230 230 ...
  ..$ Gemeinde              : chr [1:7290] "Winterthur" "Winterthur" "Winterthur" "Winterthur" ...
  ..$ Wahljahr              : num [1:7290] 2018 2018 2018 2018 2018 ...
  ..$ Einwohner             : num [1:7290] 110912 110912 110912 110912 110912 ...
  ..$ Gemeindegrösse_Cluster: chr [1:7290] "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" ...
  ..$ Partei                : chr [1:7290] "FDP 1)" "FDP 1)" "FDP 1)" "CVP" ...
  ..$ m_w_Total             : chr [1:7290] "Frauen" "Männer" "Total" "Frauen" ...
  ..$ Wert                  : num [1:7290] 1 1 2 0 1 1 2 1 3 NA ...
 $ 2019: tibble [14,580 × 11] (S3: tbl_df/tbl/data.frame)
  ..$ Kantons-Nr.           : chr [1:14580] "1" "1" "1" "1" ...
  ..$ Kanton                : chr [1:14580] "ZH" "ZH" "ZH" "ZH" ...
  ..$ Gemeinde-Nr.          : num [1:14580] 230 230 230 230 230 230 230 230 230 230 ...
  ..$ Gemeinde              : chr [1:14580] "Winterthur" "Winterthur" "Winterthur" "Winterthur" ...
  ..$ Einwohner             : num [1:14580] 111851 111851 111851 111851 111851 ...
  ..$ Grössenklasse-Nr.     : chr [1:14580] "4" "4" "4" "4" ...
  ..$ Grössenklasse         : chr [1:14580] "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" ...
  ..$ Gemeindegrösse_Cluster: chr [1:14580] "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" "≥ 100'000 Einwohnerinnen und Einwohner" ...
  ..$ Partei                : chr [1:14580] "FDP 1)" "FDP 1)" "FDP 1)" "CVP" ...
  ..$ m_w_Total             : chr [1:14580] "Frauen" "Männer" "Total" "Frauen" ...
  ..$ Wert                  : num [1:14580] 1 1 2 0 1 1 2 1 3 NA ...
Code anzeigen
lapply(daten_liste, summary)
$`2024`
 Kantons-Nr.           Kanton           Gemeinde-Nr.    Gemeinde        
 Length:7515        Length:7515        Min.   :   2   Length:7515       
 Class :character   Class :character   1st Qu.: 581   Class :character  
 Mode  :character   Mode  :character   Median :2769   Mode  :character  
                                       Mean   :2936                     
                                       3rd Qu.:5113                     
                                       Max.   :6711                     
                                                                        
    Wahljahr      Einwohner      Gemeindegrösse_Cluster    Partei         
 Min.   :2020   Min.   :  4957   Length:7515            Length:7515       
 1st Qu.:2021   1st Qu.: 11680   Class :character       Class :character  
 Median :2022   Median : 15763   Mode  :character       Mode  :character  
 Mean   :2022   Mean   : 25286                                            
 3rd Qu.:2024   3rd Qu.: 22110                                            
 Max.   :2024   Max.   :423193                                            
                                                                          
  m_w_Total              Wert      
 Length:7515        Min.   : 0.00  
 Class :character   1st Qu.: 1.00  
 Mode  :character   Median : 1.00  
                    Mean   : 1.73  
                    3rd Qu.: 2.00  
                    Max.   :30.00  
                    NA's   :4986   

$`2023`
 Kantons-Nr.           Kanton           Gemeinde-Nr.    Gemeinde        
 Length:7776        Length:7776        Min.   :   2   Length:7776       
 Class :character   Class :character   1st Qu.: 546   Class :character  
 Mode  :character   Mode  :character   Median :2770   Mode  :character  
                                       Mean   :2952                     
                                       3rd Qu.:5192                     
                                       Max.   :6711                     
                                                                        
    Wahljahr      Einwohner      Gemeindegrösse_Cluster    Partei         
 Min.   :2020   Min.   :  4957   Length:7776            Length:7776       
 1st Qu.:2020   1st Qu.: 12104   Class :character       Class :character  
 Median :2021   Median : 15754   Mode  :character       Mode  :character  
 Mean   :2021   Mean   : 25369                                            
 3rd Qu.:2022   3rd Qu.: 22193                                            
 Max.   :2023   Max.   :423193                                            
                                                                          
  m_w_Total              Wert       
 Length:7776        Min.   : 0.000  
 Class :character   1st Qu.: 1.000  
 Mode  :character   Median : 1.000  
                    Mean   : 1.719  
                    3rd Qu.: 2.000  
                    Max.   :30.000  
                    NA's   :5316    

$`2022`
 Kantons-Nr.           Kanton           Gemeinde-Nr.    Gemeinde        
 Length:7776        Length:7776        Min.   :   2   Length:7776       
 Class :character   Class :character   1st Qu.: 546   Class :character  
 Mode  :character   Mode  :character   Median :2770   Mode  :character  
                                       Mean   :2952                     
                                       3rd Qu.:5192                     
                                       Max.   :6711                     
                                                                        
    Wahljahr      Einwohner      Gemeindegrösse_Cluster    Partei         
 Min.   :2019   Min.   :  4957   Length:7776            Length:7776       
 1st Qu.:2020   1st Qu.: 12104   Class :character       Class :character  
 Median :2021   Median : 15754   Mode  :character       Mode  :character  
 Mean   :2021   Mean   : 25353                                            
 3rd Qu.:2022   3rd Qu.: 22193                                            
 Max.   :2022   Max.   :423193                                            
                                                                          
  m_w_Total              Wert       
 Length:7776        Min.   : 0.000  
 Class :character   1st Qu.: 1.000  
 Mode  :character   Median : 1.000  
                    Mean   : 1.725  
                    3rd Qu.: 2.000  
                    Max.   :30.000  
                    NA's   :5325    

$`2021`
 Kantons-Nr.           Kanton           Gemeinde-Nr.    Gemeinde        
 Length:7776        Length:7776        Min.   :   2   Length:7776       
 Class :character   Class :character   1st Qu.: 546   Class :character  
 Mode  :character   Mode  :character   Median :2770   Mode  :character  
                                       Mean   :2952                     
                                       3rd Qu.:5192                     
                                       Max.   :6711                     
                                                                        
    Wahljahr      Einwohner      Gemeindegrösse_Cluster    Partei         
 Min.   :2017   Min.   :  4994   Length:7776            Length:7776       
 1st Qu.:2018   1st Qu.: 12068   Class :character       Class :character  
 Median :2020   Median : 15754   Mode  :character       Mode  :character  
 Mean   :2020   Mean   : 25043                                            
 3rd Qu.:2021   3rd Qu.: 21718                                            
 Max.   :2021   Max.   :409241                                            
                                                                          
  m_w_Total              Wert       
 Length:7776        Min.   : 0.000  
 Class :character   1st Qu.: 1.000  
 Mode  :character   Median : 1.000  
                    Mean   : 1.722  
                    3rd Qu.: 2.000  
                    Max.   :30.000  
                    NA's   :5316    

$`2020`
 Kantons-Nr.           Kanton           Gemeinde-Nr.    Gemeinde        
 Length:7290        Length:7290        Min.   :   2   Length:7290       
 Class :character   Class :character   1st Qu.: 546   Class :character  
 Mode  :character   Mode  :character   Median :2770   Mode  :character  
                                       Mean   :2951                     
                                       3rd Qu.:5192                     
                                       Max.   :6711                     
                                                                        
    Wahljahr      Einwohner      Gemeindegrösse_Cluster    Partei         
 Min.   :2016   Min.   :  4994   Length:7290            Length:7290       
 1st Qu.:2017   1st Qu.: 11572   Class :character       Class :character  
 Median :2018   Median : 15710   Mode  :character       Mode  :character  
 Mean   :2018   Mean   : 24871                                            
 3rd Qu.:2020   3rd Qu.: 21090                                            
 Max.   :2020   Max.   :409241                                            
                                                                          
  m_w_Total              Wert       
 Length:7290        Min.   : 0.000  
 Class :character   1st Qu.: 1.000  
 Mode  :character   Median : 1.000  
                    Mean   : 1.717  
                    3rd Qu.: 2.000  
                    Max.   :30.000  
                    NA's   :4827    

$`2019`
 Kantons-Nr.           Kanton           Gemeinde-Nr.    Gemeinde        
 Length:14580       Length:14580       Min.   :   2   Length:14580      
 Class :character   Class :character   1st Qu.: 546   Class :character  
 Mode  :character   Mode  :character   Median :2770   Mode  :character  
                                       Mean   :2951                     
                                       3rd Qu.:5192                     
                                       Max.   :6711                     
                                                                        
   Einwohner      Grössenklasse-Nr.  Grössenklasse      Gemeindegrösse_Cluster
 Min.   :  4928   Length:14580       Length:14580       Length:14580          
 1st Qu.: 11733   Class :character   Class :character   Class :character      
 Median : 15693   Mode  :character   Mode  :character   Mode  :character      
 Mean   : 25006                                                               
 3rd Qu.: 21506                                                               
 Max.   :415367                                                               
                                                                              
    Partei           m_w_Total              Wert      
 Length:14580       Length:14580       Min.   : 0.00  
 Class :character   Class :character   1st Qu.: 0.00  
 Mode  :character   Mode  :character   Median : 0.00  
                                       Mean   : 0.65  
                                       3rd Qu.: 1.00  
                                       Max.   :30.00  
                                       NA's   :8084   
Code anzeigen
# Alle tibbles zu einem Dataframe zusammenfügen
elec_gemeinde_exekutiven_combined <- bind_rows(daten_liste,
                                            .id = "Sheet") %>% 
  select(-c('Grössenklasse-Nr.', Grössenklasse))



datatable(elec_gemeinde_exekutiven_combined,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))
Code anzeigen
# TODO:
# Parteinamen bereinigen!
# finales file: elec_gemeinde_exekutiven_final


# Dataframe als Excel schreiben
# write_xlsx(elec_gemeinde_exekutiven_final,
#            "elec_gemeinde_exekutiven_final.xlsx")

3.4 Parteilandschaft

Code anzeigen
#| label: load_parties_rating

parties_rating <- read_excel("~/CAS/Zertifikatsarbeit/data/parties_economic_socio-political_rating.xlsx",
                             sheet = "matrix_eco-socio_enhanced")

4 Transformation & visualization

4.1 Abstimmungen

4.2 Wahlen

4.2.1 Bundesebene

4.2.1.1 Nationalrat

Code anzeigen
########################################################################
####################### BACKUP 04.06.2025 ##############################
########################################################################


# Anzeige mit datatable()
datatable(elec_nationalrat,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))
Code anzeigen
# Fehlende Werte (NA), Klassen und Levels prüfen
Abstract(elec_nationalrat)
────────────────────────────────────────────────────────────────────────────── 
elec_nationalrat

data frame: 478 obs. of  21 variables
        0 complete cases (0.0%)

  Nr  Class  ColName                NAs           Levels
  1   log    Active                   .                 
  2   chr    FirstName                .                 
  3   chr    LastName                 .                 
  4   chr    GenderAsString           .                 
  5   chr    CantonName               .                 
  6   chr    CantonAbbreviation       .                 
  7   chr    CouncilName              .                 
  8   chr    ParlGroupName            2 (0.4%)          
  9   chr    ParlGroupAbbreviation    2 (0.4%)          
  10  chr    PartyName                .                 
  11  chr    PartyAbbreviation        .                 
  12  chr    MaritalStatusText      313 (65.5%)         
  13  chr    Nationality              1 (0.2%)          
  14  chr    BirthPlace_City          2 (0.4%)          
  15  chr    BirthPlace_Canton       17 (3.6%)          
  16  chr    Mandates                46 (9.6%)          
  17  chr    DateJoining              .                 
  18  chr    DateLeaving            200 (41.8%)         
  19  chr    Citizenship              4 (0.8%)          
  20  chr    DateOfBirth              .                 
  21  log    DateOfDeath            478 (100.0%)        
Code anzeigen
PlotMiss(elec_nationalrat)

Code anzeigen
# Datensatz reduzieren auf Aktive und doppelte Einträge elimnieren
elec_nationalrat_reduced <- elec_nationalrat %>%
  filter(Active == TRUE) %>% 
  distinct(.keep_all = TRUE) %>% 
  select(CantonAbbreviation,
         CantonName,
         PartyAbbreviation,
         PartyName) %>% 
  mutate(value = 1)

datatable(elec_nationalrat_reduced,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))
Code anzeigen
# Sitze pro Kanton, Partei und Jahr 
    
    # long format
      df_sum <- elec_nationalrat_reduced %>%
      group_by(CantonAbbreviation, PartyAbbreviation) %>%
      summarise(sum_value = sum(value, na.rm = TRUE),
                .groups = "drop")
    
    # wide format
    # Pivotieren: Partei-Spalten erzeugen
    df_wide <- df_sum %>%
      pivot_wider(names_from = PartyAbbreviation,
                  values_from = sum_value,
                  values_fill = 0)
    
    
    
    # Partei-Spaltennamen extrahieren (ohne Kanton/Jahr)
    partei_cols <- setdiff(names(df_wide),
                           c("CantonAbbreviation"))
    
    # Spaltensummen der Parteien berechnen für Sortierung
    partei_sums <- colSums(df_wide[partei_cols])
    
    # Zeilensumme der Kantone/Jahre hinzufügen
    df_wide$Total <- rowSums(df_wide[partei_cols])
    
    
    # Parteispalten-Anordnung nach Summe sortieren
    sorted_partei <- names(sort(partei_sums,
                                decreasing = TRUE))
    
    
    # Dataframe neu anordnen: Kanton/Jahr,  sortierte und gefilterte Parteien
    elec_nationalrat_final <- df_wide[, c(setdiff(names(df_wide),
                                                  partei_cols),
                                          sorted_partei)] %>% 
      arrange(desc(Total))
    
datatable(elec_nationalrat_final,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))

4.2.1.2 Ständerat

Code anzeigen
########################################################################
####################### BACKUP 04.06.2025 ##############################
########################################################################



#TODO Es kann innerhalb der Legislatur Wechsel geben --> Anstelle der heute "Aktiven" ist es deshalb genauer die Räte/Konstellation zum Zeitpunkt der jeweiligen Abstimmung zu prüfen.


# Anzeige mit datatable()
datatable(elec_ständerat,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))
Code anzeigen
# Fehlende Werte (NA), Klassen und Levels prüfen
Abstract(elec_ständerat)
────────────────────────────────────────────────────────────────────────────── 
elec_ständerat

data frame: 155 obs. of  21 variables
        0 complete cases (0.0%)

  Nr  Class  ColName                NAs           Levels
  1   log    Active                   .                 
  2   chr    FirstName                .                 
  3   chr    LastName                 .                 
  4   chr    GenderAsString           .                 
  5   chr    CantonName               .                 
  6   chr    CantonAbbreviation       .                 
  7   chr    CouncilName              .                 
  8   chr    ParlGroupName            .                 
  9   chr    ParlGroupAbbreviation    .                 
  10  chr    PartyName                .                 
  11  chr    PartyAbbreviation        .                 
  12  chr    MaritalStatusText      116 (74.8%)         
  13  chr    Nationality              .                 
  14  chr    BirthPlace_City          .                 
  15  chr    BirthPlace_Canton        .                 
  16  chr    Mandates                11 (7.1%)          
  17  chr    DateJoining              .                 
  18  chr    DateLeaving             45 (29.0%)         
  19  chr    Citizenship              .                 
  20  chr    DateOfBirth              .                 
  21  log    DateOfDeath            155 (100.0%)        
Code anzeigen
PlotMiss(elec_ständerat)

Code anzeigen
# Datensatz reduzieren auf Aktive und doppelte Einträge elimnieren
elec_ständerat_reduced <- elec_ständerat %>%
  filter(Active == TRUE) %>% 
  distinct(.keep_all = TRUE) %>% 
  select(CantonAbbreviation,
         CantonName,
         PartyAbbreviation,
         PartyName) %>% 
  mutate(value = 1)

datatable(elec_ständerat_reduced,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))
Code anzeigen
# Sitze pro Kanton, Partei und Jahr 
    
    # long format
      df_sum <- elec_ständerat_reduced %>%
      group_by(CantonAbbreviation, PartyAbbreviation) %>%
      summarise(sum_value = sum(value, na.rm = TRUE),
                .groups = "drop")
    
    # wide format
    # Pivotieren: Partei-Spalten erzeugen
    df_wide <- df_sum %>%
      pivot_wider(names_from = PartyAbbreviation,
                  values_from = sum_value,
                  values_fill = 0)
    
    
    # Partei-Spaltennamen extrahieren (ohne Kanton/Jahr)
    partei_cols <- setdiff(names(df_wide),
                           c("CantonAbbreviation"))
    
    # Spaltensummen der Parteien berechnen für Sortierung
    partei_sums <- colSums(df_wide[partei_cols])
    
    # Zeilensumme der Kantone/Jahre hinzufügen
    df_wide$Total <- rowSums(df_wide[partei_cols])
    
    
    # Parteispalten-Anordnung nach Summe sortieren
    sorted_partei <- names(sort(partei_sums,
                                decreasing = TRUE))
    
    
    # Dataframe neu anordnen: Kanton/Jahr,  sortierte und gefilterte Parteien
    elec_ständerat_final <- df_wide[, c(setdiff(names(df_wide),
                                                  partei_cols),
                                        sorted_partei)] %>% 
      arrange(desc(Total))
    
datatable(elec_ständerat_final,
          class = 'nowrap',
          filter = 'top',
          options = list(pageLength = 7,
                         scrollX = TRUE,
                         search = list(regex = TRUE,
                                       caseInsensitive = TRUE)))

4.2.2 Kantonsebene

4.2.2.1 Kantonsregierung

Code anzeigen
########################################################################
####################### BACKUP 03.06.2025 ##############################
########################################################################

# 
# datatable(elec_kantonale_regierung_final,
#          class = 'nowrap',
#           filter = 'top',
#           options = list(pageLength = 7,
#                          scrollX = TRUE,
#                          search = list(regex = TRUE,
#                                        caseInsensitive = TRUE)))
# 
# # Änderungen verfolgen
# # Partei-Spalten bestimmen
# parteien <- setdiff(names(elec_kantonsregierung_final), c("Kanton", "Wahljahr", "Total"))
# 
# # Dataframe nach Kanton und Wahljahr sortieren und ins lange Format bringen
# df_long <- elec_kantonsregierung_final %>%
#   arrange(Kanton, Wahljahr) %>%
#   pivot_longer(
#     cols = all_of(parteien),
#     names_to = "Partei",
#     values_to = "Wert_neu"
#   ) %>%
#   group_by(Kanton, Partei) %>%
#   arrange(Wahljahr, .by_group = TRUE) %>%
#   mutate(Wert_alt = lag(Wert_neu)) %>%
#   ungroup()
# 
# # Änderungen filtern: Zeige nur, wo sich der Wert geändert hat (und nicht NA)
# elec_kantonsregierung_changelog <- df_long %>%
#   filter(!is.na(Wert_alt),
#          Wert_neu != Wert_alt) %>%
#   select(Kanton,
#          Wahljahr,
#          Partei,
#          Wert_alt,
#          Wert_neu)
# 
# 
# datatable(elec_kantonsregierung_changelog,
#           class = 'nowrap',
#           filter = 'top',
#           options = list(pageLength = 7,
#                          scrollX = TRUE,
#                          search = list(regex = TRUE,
#                                        caseInsensitive = TRUE)))

4.3 Parteilandschaft

4.3.1 Multi-Dimensions-Model

4.3.1.1 Minimalwerte

Für jede Partei den Minimalwert des Abstimmungsverhaltens zu verwenden, basiert auf der Zielsetzung, die klarste politische Position einer Partei in einem bestimmten Themenbereich zu identifizieren. Diese Methodik stellt sicher, dass auch bei wenigen Abweichungen von der Mehrheitslinie die tatsächliche Haltung der Partei deutlich erkennbar bleibt.

Code anzeigen
ggplot(parties_rating,
       aes(x = eco_pct_min_x,
           y = socio_pct_min_y,
           label = Partei)) +
  geom_point(size = 3) +
  geom_text(vjust = -0.8) +
  geom_segment(aes(x = 50, xend = 50,
                   y = 0, yend = 100), linetype = "dashed", color = "blue") +
  geom_segment(aes(x = 0, xend = 100,
                   y = 50, yend = 50), linetype = "dashed", color = "blue") +
  
  annotate("text",
           x = 0,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Links/staatsgläubig", size = 4, hjust = 0.5) +
  annotate("text",
           x = 100,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Rechts/marktwirtschaftlich", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) - 3,
           label = "Autoritär/Konservativ", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) + 103,
           label = "Libertär/Progressiv", size = 4, hjust = 0.5) +
  scale_x_continuous(limits = c(0, 100)) +
  labs(x = "Wirtschaftspolitisch",
       y = "Gesellschaftspolitisch",
       title = "Politische Positionierung im Zwei-Achsen-Modell",
       subtitle = "Grundlage: Minimalwerte des Abstimmungsverhaltens") +
  theme_minimal()+
  theme(plot.title = element_text(size = 18,
                                  margin = margin(b = 30)),
        axis.title.x = element_text(size = 14,
                                    margin = margin(t = 5)),
        axis.title.y = element_text(size = 14,
                                    margin = margin(r = 5))) +
  coord_cartesian(ylim = c(0, 105),
                  xlim = c(0, 105),
                  clip = "off")

4.3.1.2 Durchschnittswerte als Zentrum und Min/Max als Ellipse

Code anzeigen
ggplot(parties_rating,
       aes(x = eco_pct_avg_x,
           y = socio_pct_avg_y,
           label = Partei)) +
  geom_ellipse(
    aes(x0 = eco_pct_avg_x,
        y0 = socio_pct_avg_y,
        a = (eco_pct_max_x - eco_pct_min_x) / 2, # Halbachse x
        b = (socio_pct_max_y - socio_pct_min_y) / 2, # Halbachse y
        angle = 0),
    fill = "gray80", alpha = 0.4) +
  geom_point(size = 3) +
  geom_text(vjust = -0.8) +
  geom_segment(aes(x = 50, xend = 50,
                   y = 0, yend = 100), linetype = "dashed", color = "blue") +
  geom_segment(aes(x = 0, xend = 100,
                   y = 50, yend = 50), linetype = "dashed", color = "blue") +
  annotate("text",
           x = 0,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Links/staatsgläubig", size = 4, hjust = 0.5) +
  annotate("text",
           x = 100,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Rechts/marktwirtschaftlich", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) - 3,
           label = "Autoritär/Konservativ", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) + 103,
           label = "Libertär/Progressiv", size = 4, hjust = 0.5) +
  scale_x_continuous(limits = c(-10, +110)) +
  labs(x = "Wirtschaftspolitisch",
       y = "Gesellschaftspolitisch",
       title = "Politische Positionierung im Zwei-Achsen-Modell",
       subtitle = "Grundlage: Durchschnitt des Abstimmungsverhaltens als Zentrum, Min-/Max als Ellipse") +
  theme_minimal()+
  theme(plot.title = element_text(size = 18,
                                  margin = margin(b = 30)),
        axis.title.x = element_text(size = 14,
                                    margin = margin(t = 5)),
        axis.title.y = element_text(size = 14,
                                    margin = margin(r = 5))) +
  coord_cartesian(ylim = c(0, 100),
                  xlim = c(0, 100),
                  clip = "off")

4.3.1.3 Hauptparteien: Durchschnittswerte als Zentrum und Min/Max als Ellipse / Klein- und Regionalparteien nur mit ihrem Durchschnittswert

Code anzeigen
ggplot(parties_rating,
       aes(x = eco_pct_avg_x,
           y = socio_pct_avg_y,
           label = Partei)) +
  geom_ellipse(aes(x0 = eco_pct_avg_x,
                   y0 = socio_pct_avg_y,
                   a = (eco_pct_max_x - eco_pct_min_x) / 2, # Halbachse x
                   b = (socio_pct_max_y - socio_pct_min_y) / 2, # Halbachse y
                   angle = 0),
               fill = "gray80", alpha = 0.4) +
  geom_point(size = 3) +
  
  # Kleinere Parteien
  geom_point(data = subset(parties_rating,
                           `Grosse Partei` == "n" &
                             Relevanz_nationale_Abstimmungen != "-"),
             aes(x = eco_x,
                 y = socio_y),
             shape = 21, fill = "grey", color = "black", size = 1, stroke = 1) +
  
  # Labels der kleineren Partein
  geom_text(data = subset(parties_rating,
                          `Grosse Partei` == "n" &
                            Relevanz_nationale_Abstimmungen != "-"),
            aes(x = eco_x,
                y = socio_y,
                label = Partei),
            vjust = -1, fontface = "plain", color = "darkgrey") +
  geom_text(vjust = -0.8) +
  geom_segment(aes(x = 50, xend = 50,
                   y = 0, yend = 100), linetype = "dashed", color = "blue") +
  geom_segment(aes(x = 0, xend = 100,
                   y = 50, yend = 50), linetype = "dashed", color = "blue") +
  annotate("text",
           x = 0,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Links/staatsgläubig", size = 4, hjust = 0.5) +
  annotate("text",
           x = 100,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Rechts/marktwirtschaftlich", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) - 3,
           label = "Autoritär/Konservativ", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) + 103,
           label = "Libertär/Progressiv", size = 4, hjust = 0.5) +
  scale_x_continuous(limits = c(-10, +110)) +
  labs(x = "Wirtschaftspolitisch",
       y = "Gesellschaftspolitisch",
       title = "Politische Positionierung im Zwei-Achsen-Modell",
       subtitle = "Durchschnitt des Abstimmungsverhaltens als Zentrum, Min-/Max als Ellipse") +
  theme_minimal()+
  theme(plot.title = element_text(size = 18,
                                  margin = margin(b = 30)),
        axis.title.x = element_text(size = 14,
                                    margin = margin(t = 5)),
        axis.title.y = element_text(size = 14,
                                    margin = margin(r = 5))) +
  coord_cartesian(ylim = c(0, 100),
                  xlim = c(0, 100),
                  clip = "off")

4.3.1.4 Hauptparteien: Durchschnittswerte als Zentrum und Min/Max als Ellipse / Verbände sowie Klein- und Regionalparteien nur mit ihrem Durchschnittswert

Code anzeigen
ggplot(parties_rating,
       aes(x = eco_pct_avg_x,
           y = socio_pct_avg_y,
           label = Partei)) +
  geom_ellipse(
    aes(x0 = eco_pct_avg_x,
        y0 = socio_pct_avg_y,
        a = (eco_pct_max_x - eco_pct_min_x) / 2, # Halbachse x
        b = (socio_pct_max_y - socio_pct_min_y) / 2, # Halbachse y
        angle = 0),
    fill = "gray80", alpha = 0.4) +
  geom_point(size = 3) +
  
  # Kleinere Parteien
  geom_point(data = subset(parties_rating,
                           `Grosse Partei` == "n" #&
                           #Relevanz_nationale_Abstimmungen != "-"
                           )
                  ,
    aes(x = eco_x, y = socio_y),
    shape = 21, fill = "grey", color = "black", size = 1, stroke = 1
  ) +
  # Labels der kleineren Partein
  geom_text(data = subset(parties_rating,
                          `Grosse Partei` == "n" #&
                          #Relevanz_nationale_Abstimmungen != "-"
                  )
                  ,
    aes(x = eco_x, y = socio_y, label = Partei),
    vjust = -1, fontface = "plain", color = "darkgrey"
  ) +

  geom_text(vjust = -0.8) +
  geom_segment(aes(x = 50, xend = 50,
                   y = 0, yend = 100), linetype = "dashed", color = "blue") +
  geom_segment(aes(x = 0, xend = 100,
                   y = 50, yend = 50), linetype = "dashed", color = "blue") +
  annotate("text",
           x = 0,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Links/staatsgläubig", size = 4, hjust = 0.5) +
  annotate("text",
           x = 100,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Rechts/marktwirtschaftlich", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) - 3,
           label = "Autoritär/Konservativ", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) + 103,
           label = "Libertär/Progressiv", size = 4, hjust = 0.5) +
  scale_x_continuous(limits = c(-10, +110)) +
  labs(x = "Wirtschaftspolitisch",
       y = "Gesellschaftspolitisch",
       title = "Politische Positionierung im Zwei-Achsen-Modell",
       subtitle = "Grundlage: Durchschnitt des Abstimmungsverhaltens als Zentrum, Min-/Max als Ellipse") +
  theme_minimal()+
  theme(plot.title = element_text(size = 18,
                                  margin = margin(b = 30)),
        axis.title.x = element_text(size = 14,
                                    margin = margin(t = 5)),
        axis.title.y = element_text(size = 14,
                                    margin = margin(r = 5))) +
  coord_cartesian(ylim = c(0, 100),
                  xlim = c(0, 100),
                  clip = "off")

4.3.2 3D-Koordinatensystem (Testweise)

Code anzeigen
#install.packages("plotly")

library(plotly)

# Datenframe erstellen
parties <- data.frame(
  Partei = c("SP", "SVP", "FDP", "GPS", "GLP", "Mitte", "EVP", "EDU"),
  Wirtschaft = c(20, 85, 80, 30, 60, 60, 40, 90),      # Links = niedrig, Rechts = hoch
  Gesellschaft = c(90, 20, 55, 90, 75, 45, 50, 20),    # Libertär = hoch, Autoritär = niedrig
  Kosmopolitismus = c(90, 15, 60, 95, 85, 50, 55, 20)  # Kosmopolitisch = hoch, Nationalistisch = niedrig
)

# Interaktives 3D-Scatterplot mit Plotly
fig <- plot_ly(
  data = parties,
  x = ~Wirtschaft,
  y = ~Gesellschaft,
  z = ~Kosmopolitismus,
  text = ~Partei,
  type = 'scatter3d',
  mode = 'markers+text',
  marker = list(size = 5),
  textposition = 'top center'
)

# Achsen benennen und Layout anpassen
fig <- fig %>% layout(
  scene = list(
    xaxis = list(title = "Wirtschaftspolitik (links – rechts)"),
    yaxis = list(title = "Gesellschaftspolitik (autoritär – libertär)"),
    zaxis = list(title = "Kosmopolitismus – Nationalismus")
  ),
  title = "Schweizer Parteien im 3D-Koordinatensystem nach Kitschelt"
)

# Plot anzeigen
fig